﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Web.Services.Description;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.CodeDom;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Dynamic;
using System.Reflection;
using System.Web.Services.Protocols;

namespace IronTrishul.Discovery
{
    public class WSDLManager
    {
        string userName;

        public string UserName
        {
            get { return userName; }
            set { userName = value; }
        }
        string password;

        public string Password
        {
            get { return password; }
            set { password = value; }
        }
        string wsdlUrl;

        public string WsdlUrl
        {
            get { return wsdlUrl; }
            set { wsdlUrl = value; }
        }


        string assemblyName = null;

        public string AssemblyName
        {
            get { return assemblyName; }
            set { assemblyName = value; }
        }
        AppDomain localAppDomain = null;

        public AppDomain LocalAppDomain
        {
            get { return localAppDomain; }
            set { localAppDomain = value; }
        }
        string errorMessage = "";

        public string ErrorMessage
        {
            get { return errorMessage; }
            set { errorMessage = value; }
        }
        ServiceDescription sd = null;

        public ServiceDescription WSDLDesciription
        {
            get { return sd; }
            set { sd = value; }
        }
        ServiceDescriptionImporter importer = null;
        CodeNamespace ns = null;

        public CodeNamespace CodeNamespace
        {
            get { return ns; }
            set { ns = value; }
        }
        private Assembly generatedAssembly;

        public Assembly GeneratedAssembly
        {
            get
            {
                if (generatedAssembly == null)
                {
                    if (wsdlUrl == null)
                        throw new Exception("You have not set the WSDL URL.");
                  proxy =  GetProxy();
                }
                return generatedAssembly;
            }
            set { generatedAssembly = value; }
        }
        string serviceTypeName;

        public string ServiceTypeName
        {
            get { return serviceTypeName; }
            set { serviceTypeName = value; }
        }

        dynamic proxy;

        private dynamic Proxy
        {
            get {
                if (proxy == null)
                {
                    proxy = GetProxy();
                }
                return proxy; 
            }
            set { 
                proxy = value; 
            }
        }
        public WSDLManager()
        {

        }
        public WSDLManager(string wsdlUrl, string username, string password)
        {
            this.wsdlUrl = wsdlUrl;
            this.userName = username;
            this.password = password;
        }
        public void Initialize(string generatedNamespace)
        {
            ns = new CodeNamespace(generatedNamespace);
            importer = new ServiceDescriptionImporter();
            importer.ProtocolName = "SOAP";
            //serviceTypeName = generatedNamespace + "." + serviceName;
            importer.CodeGenerationOptions = CodeGenerationOptions.None;
            //importer.CodeGenerationOptions = CodeGenerationOptions.GenerateNewAsync | CodeGenerationOptions.GenerateOldAsync;
        }
        public CodeCompileUnit GenerateWsdl(string generatedNamespace)
        {
            Initialize(generatedNamespace );
            //this.wsdlUrl = wsdlUrl;
            //this.userName = username;
            //this.password = password;
            // download the WSDL content into a service description
            WebClient http = new WebClient();
            if (!string.IsNullOrEmpty(userName))
            {
                http.Proxy = WebRequest.DefaultWebProxy;
                //http.Proxy = WebProxy.GetDefaultProxy();// HttpWebRequest.DefaultWebProxy;
                http.Proxy.Credentials = new NetworkCredential("biswaspi", "June12345","psasnoida");
            }
            try
            {
               
                 string data =   http.DownloadString(wsdlUrl);
                 if (data.IndexOf("<wsdl:definitions") > 1)
                 {
                     sd = ServiceDescription.Read(new StringReader(data));
                 }
                 else
                 {
                     throw new Exception("Error..not a wsdl file");
                 }
            }
            catch (Exception ex)
            {
                this.ErrorMessage = "Wsdl Download failed: " + ex.Message;
                // return null;
            }

            // create an importer and associate with the ServiceDescription

            importer.AddServiceDescription(sd, null, null);
            //First Service...
            serviceTypeName = sd.Services[0].Name;

            // Download and inject any imported schemas (ie. WCF generated WSDL)            
            foreach (XmlSchema wsdlSchema in sd.Types.Schemas)
            {
                // Loop through all detected imports in the main schema
                foreach (XmlSchemaObject externalSchema in wsdlSchema.Includes)
                {
                    // Read each external schema into a schema object and add to importer
                    if (externalSchema is XmlSchemaImport)
                    {
                        Uri baseUri = new Uri(wsdlUrl);
                        Uri schemaUri = new Uri(baseUri, ((XmlSchemaExternal)externalSchema).SchemaLocation);

                        Stream schemaStream = http.OpenRead(schemaUri);
                        System.Xml.Schema.XmlSchema schema = XmlSchema.Read(schemaStream, null);
                        importer.Schemas.Add(schema);
                    }
                }
            }

            // set up for code generation by creating a namespace and adding to importer

            CodeCompileUnit ccu = new CodeCompileUnit();
            ccu.Namespaces.Add(ns);
            importer.Import(ns, ccu);
            return ccu;
        }

        public object GetProxy(string namespaceString)
        {
            CodeCompileUnit codeCompileUnit = GenerateWsdl(namespaceString);
            return GetProxy(codeCompileUnit);
        }

        public object GetProxy()
        {
            return GetProxy("AUTO");
        }
        public object GetProxy(CodeCompileUnit codeCompileUnit)
        {
            string wsProxyTypeName = FindProxyTypeAndAugmentCodeDom(ns);
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CS");
            CompilerParameters compilerParams = new CompilerParameters();
            compilerParams.GenerateInMemory = true;
            compilerParams.IncludeDebugInformation = false;
            compilerParams.ReferencedAssemblies.Add(typeof(Microsoft.Scripting.Hosting.ScriptRuntime).Assembly.Location); //DLR
            CompilerResults results = provider.CompileAssemblyFromDom(compilerParams, codeCompileUnit);
            if (results.Errors.Count > 0)
            {
                throw new Exception("Error while compiling" +  results.Errors[0].ToString() );
            }

            generatedAssembly = results.CompiledAssembly;
            // find the type derived from SoapHttpClientProtocol
            Type wsProxyType = generatedAssembly.GetType(wsProxyTypeName) ;
            if (wsProxyType == null)
            {
                wsProxyType = generatedAssembly.GetType( ns.Name + "." +  wsProxyTypeName);
                if (wsProxyType == null)
                {
                    throw new InvalidOperationException("Web service proxy type not generated.");
                }
            }
            // create an instance of the web proxy type
            return Activator.CreateInstance(wsProxyType);
        }
        string FindProxyTypeAndAugmentCodeDom(CodeNamespace codeNamespace)
        {
            // add new type containing Type properties for each type
            // in the web service proxy assembly (kind of a namespace)
            string nsName = string.Format("ws_namespace_{0:x}", Guid.NewGuid().GetHashCode());
            CodeTypeDeclaration nsType = new CodeTypeDeclaration(nsName);

            CodeTypeDeclaration wsType = null; // the web service type (only one)

            foreach (CodeTypeDeclaration t in codeNamespace.Types)
            {
                string name = t.Name;

                // find the one derived from SoapHttpClientProtocol
                foreach (CodeTypeReference baseType in t.BaseTypes)
                {
                    if (baseType.BaseType == typeof(SoapHttpClientProtocol).FullName)
                    {
                        if (wsType != null)
                        {
                            throw new InvalidDataException("Found more than one web service proxy type.");
                        }

                        wsType = t;
                    }
                }

                // add the corresponding property to the namespace type
                CodeMemberProperty p = new CodeMemberProperty();
                p.Attributes &= ~MemberAttributes.AccessMask;
                p.Attributes |= MemberAttributes.Public;
                p.Name = name; // same as type name
                p.Type = new CodeTypeReference(typeof(Type));
                p.GetStatements.Add(new CodeMethodReturnStatement(new CodeTypeOfExpression(name)));
                nsType.Members.Add(p);
            }

            if (wsType == null)
            {
                // must have exactly one ws proxy
                throw new InvalidDataException("Web service proxy type not found.");
            }

            codeNamespace.Types.Add(nsType);

            // add ServiceNamespace property of the above type to the proxy type
            CodeMemberField nsField = new CodeMemberField(nsName, "_serviceNamespace");
            nsField.Attributes &= ~MemberAttributes.AccessMask;
            nsField.Attributes |= MemberAttributes.Private;
            nsField.InitExpression = new CodeObjectCreateExpression(nsName);
            wsType.Members.Add(nsField);

            CodeMemberProperty nsProp = new CodeMemberProperty();
            nsProp.Attributes &= ~MemberAttributes.AccessMask;
            nsProp.Attributes |= MemberAttributes.Public;
            nsProp.Name = "ServiceNamespace";
            nsProp.Type = new CodeTypeReference(nsName);
            nsProp.GetStatements.Add(new CodeMethodReturnStatement(
                new CodePropertyReferenceExpression(
                    new CodeThisReferenceExpression(),
                    "_serviceNamespace")));
            wsType.Members.Add(nsProp);

            // return the proxy type name
            return wsType.Name;
        }
       

    }

}
